package com.demo.common.aspect;

import com.demo.common.annotation.LogDemo;
import com.demo.common.utils.IPUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Calendar;

/**
 * 日志切面类，基于此类的对象进行日志行为记录
 * 1.@Aspect 用于描述类，定义此类未切面类型
 * 2.@Service 有的人也习惯性写@Component,这两个一样
 */
@Aspect
@Component
public class LogAspect {
    /**
     * 借助 @Pointcut 定义切入点表达式，切入点
     * 表示用于告诉底层系统，哪些方法执行时
     * 要切入扩展功能（例如around）
     * within与annotation区别？
     * within：主要针对加在类上的注解，本类的所有方法都有效，不包含子类
     * annotation：主要针对加在方法上的注解
     */
//	@Pointcut("bean(sysUserServiceImpl)")
    //如果对所有的service，就需要这样改，但是所有的service就要有一致的类型
//    @Pointcut("bean(*ServiceImpl)")
    //如果我想在某个类的某个方法添加切面该怎么做你，以上这种方法是粗粒度的控制，做不到某个方法的控制
    //那么我们想要控制其中的某个方法，该怎么做呢
    //我们可以自定义注解，然后就可以使用了
    //@annotation 为注解方式的表达式，次表达式可以进行细粒度访问定义
//	@Pointcut("@annotation(com.demo.common.annotation.LogDemo)")
    @Pointcut("@within(com.demo.common.annotation.LogDemo)")
    public void doLogPoint() {
    }
    @Pointcut("@annotation(com.demo.common.annotation.LogDemo)")
    public void doLogPoint2() {
    }

    @Pointcut("doLogPoint() && doLogPoint()")
    public void testMulti(){}

    /**
     * 使用@Around修饰的方法为一个环绕通知方法
     * 当方法参数为 ProceedingJoinPoint 类型时，
     * 并且希望在方法之前，之后都有功能扩展，
     * 一般可以借助@Around注解进行修饰
     * 此方法何时执行呢？
     * 由@Around注解内使用的表达式决定
     * 其中bean()为一个切入点表达式
     * 例如bean(sysUserServiceImpl)表示
     * 当sysUserServiceImpl类中的所有方法执行时
     * 自动执行如下around方法
     *
     * @param jp 连接点
     * @return 返回值，一般返回值为目标方法的执行结果
     * @throws Throwable 执行业务时返回的异常
     */
    //bean抽出
    @Around("doLogPoint2()")
    //原始样子
//    @AfterReturning(value="doLogPoint()",returning="result")//只有AfterReturning才有返回值，returning
//    @Around("bean(sysUserServiceImpl)")
    public Object around(ProceedingJoinPoint jp) throws Throwable {
        long t1 = System.currentTimeMillis();
        Object result = jp.proceed();//执行目标方法，如果没有这一行，就不会执行目标方法
        long t2 = System.currentTimeMillis();
        printLog(jp, t2 - t1);
        return result;
    }

    private void printLog(ProceedingJoinPoint jp, long time) throws NoSuchMethodException {
        //1.获取日志信息
        //1.1获取方法签名信息（记录了慕白方法信息）
        //Signature与MethodSignature在平常使用的还是很多的，私下看一下
//		Signature signature = jp.getSignature();//获取方法签名信息
        MethodSignature signature = (MethodSignature) jp.getSignature();//获取方法签名信息
        //1.2获取目标方法参数类型
        Class<?>[] parameterTypes = signature.getParameterTypes();
        //1.3获取目标方法名字
        String methodName = signature.getName();//获取方法名
        //1.4获取目标方法对象
        Class<?> targetClass = jp.getTarget().getClass();

        //1.5获取类方法信息（类名+"."+方法名）
        String method = targetClass.getName() + "." + methodName;
        Method targetMethod = targetClass.getDeclaredMethod(methodName, parameterTypes);

        //1.6获取方法对象上的注解LogDemo
        ////假如使用的是jdk1.8以下，不要使用这个方法，使用getAnnotation
        LogDemo logDemo = targetMethod.getDeclaredAnnotation(LogDemo.class);//获取方法上的日志注解
        LogDemo logDemoClass = targetClass.getDeclaredAnnotation(LogDemo.class);//获取类上面的日志注解
        //1.7获取注解上指定的操作名字
        //现在我们用的是自定义注解，如果是没有该注解的方法，就不能获取到他的value值，就会出错
        //所以这里可以特殊处理
        String operation = "";
        if (logDemo != null) {
            operation = logDemo.value();
        } else if (logDemoClass != null) {
            operation = logDemoClass.value();
        }

        //1.8以下日志可以写到日志文件中或者数据库中
        System.out.println("*********************开始打印日志*********************");
        System.out.println("operation = " + operation);//操作名称
        System.out.println("time = " + time + "ms");//执行时间
        System.out.println("method = " + method);//执行方法
        System.out.println("args = " + Arrays.toString(jp.getArgs()));
        Calendar calendar = Calendar.getInstance(); // get current instance of the calendar
        SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        System.out.println("date = " + formatter.format(calendar.getTime()));
        String ip = IPUtils.getIpAddr();
        System.out.println("ip = " + ip);
        System.out.println("*********************日志打印结束*********************");

    }

}
